package dev.ece.core.texture;

import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL14;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL32;

import dev.ece.core.clearup.ClearUpGLRubbish;
import dev.ece.core.clearup.ClearUpGLRubbishManager;

/**
 * fbo 纹理
 * @author 飘渺青衣
 * <b>E-mail：</b>3375758@qq.com
 */
public class FBO implements ClearUpGLRubbish {
	
	private int width;
	
	private int height;
	
	private int framebuffer;
	
	private int renderbuffer;
	
	private Texture texture;
	
	private int samples;
	
	private FBO(int width, int height) throws Exception {
		this(0, width, height);
	}
	
	private FBO(int samples, int width, int height) throws Exception {
		this.width = width;
		this.height = height;
		this.samples = samples;
		int maxsize = GL11.glGetInteger(GL30.GL_MAX_RENDERBUFFER_SIZE);
		if(width > maxsize || height > maxsize) {
			throw new Exception("超出范围");
		}
	}
	
	public static FBO newDepthInstance(int width, int height) throws Exception {
		FBO fbo = new FBO(width, height);
		fbo.framebuffer = GL30.glGenFramebuffers();
		GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fbo.framebuffer);
		fbo.texture = new Texture(width, height);
		fbo.texture.bind(0);
		GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL14.GL_DEPTH_COMPONENT16, width, height, 0, GL11.GL_DEPTH_COMPONENT, GL11.GL_FLOAT, 0);
		GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
		GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
		GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL12.GL_CLAMP_TO_EDGE);
		GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL12.GL_CLAMP_TO_EDGE);
		GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL14.GL_DEPTH_TEXTURE_MODE, GL11.GL_INTENSITY);
		GL11.glAlphaFunc(GL11.GL_GREATER, 0.8f);
		GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_MODULATE);
		GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL14.GL_TEXTURE_COMPARE_MODE, GL14.GL_COMPARE_R_TO_TEXTURE);
		GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL14.GL_TEXTURE_COMPARE_FUNC, GL11.GL_LEQUAL);
		GL30.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_DEPTH_ATTACHMENT, GL11.GL_TEXTURE_2D, fbo.texture.getTexture(), 0);
		GL11.glDrawBuffer(GL30.GL_DEPTH_ATTACHMENT);
		boolean isComplete = GL30.glCheckFramebufferStatus(GL30.GL_FRAMEBUFFER) == GL30.GL_FRAMEBUFFER_COMPLETE;
		GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
		Texture.unbind();
		if(!isComplete) {
			fbo.texture.destroy();
			throw new Exception("fbo创建失败");
		}
		return fbo;
	}
	
	public static FBO newInstance(int width, int height) throws Exception {
		return newInstance(0, width, height);
	}
	
	public static FBO newHDRInstance(int width, int height) throws Exception {
		FBO fbo = new FBO(0, width, height);
		fbo.framebuffer = GL30.glGenFramebuffers();
		fbo.renderbuffer = GL30.glGenRenderbuffers();
		GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fbo.framebuffer);
		GL30.glBindRenderbuffer(GL30.GL_RENDERBUFFER, fbo.renderbuffer);
		GL30.glRenderbufferStorage(GL30.GL_RENDERBUFFER, GL14.GL_DEPTH_COMPONENT16, width, height);
		
		fbo.texture = new Texture(width, height, GL11.glGenTextures(), GL11.GL_TEXTURE_2D);
		fbo.texture.bind(0);
		
		GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL13.GL_CLAMP_TO_EDGE);
		GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL13.GL_CLAMP_TO_EDGE);
		GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
		GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
		
		GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL30.GL_RGBA16F, width, height, 0, GL11.GL_RGBA, GL11.GL_FLOAT, 0);
		
		GL30.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, fbo.texture.getTexture(), 0);
		GL11.glDrawBuffer(GL30.GL_COLOR_ATTACHMENT0);
		GL30.glFramebufferRenderbuffer(GL30.GL_FRAMEBUFFER, GL30.GL_DEPTH_ATTACHMENT, GL30.GL_RENDERBUFFER, fbo.renderbuffer);
		boolean isComplete = GL30.glCheckFramebufferStatus(GL30.GL_FRAMEBUFFER) == GL30.GL_FRAMEBUFFER_COMPLETE;
		GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
		Texture.unbind();
		if(!isComplete) {
			fbo.texture.destroy();
			throw new Exception("fbo创建失败");
		}
		return fbo;
	}
	
	public static FBO newInstance(int samples, int width, int height) throws Exception {
		FBO fbo = new FBO(samples, width, height);
		int maxsample = GL11.glGetInteger(GL30.GL_MAX_SAMPLES);
		fbo.samples = Math.min(fbo.samples, maxsample);
		int target = fbo.samples == 0 ? GL11.GL_TEXTURE_2D : GL32.GL_TEXTURE_2D_MULTISAMPLE;
		fbo.framebuffer = GL30.glGenFramebuffers();
		fbo.renderbuffer = GL30.glGenRenderbuffers();
		GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fbo.framebuffer);
		GL30.glBindRenderbuffer(GL30.GL_RENDERBUFFER, fbo.renderbuffer);
		fbo.texture = new Texture(width, height, GL11.glGenTextures(), target);
		fbo.texture.bind(0);
		GL11.glTexParameterf(target, GL11.GL_TEXTURE_WRAP_S, GL13.GL_CLAMP_TO_EDGE);
		GL11.glTexParameterf(target, GL11.GL_TEXTURE_WRAP_T, GL13.GL_CLAMP_TO_EDGE);
		GL11.glTexParameterf(target, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
		GL11.glTexParameterf(target, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
		if(fbo.samples == 0) {
			GL30.glRenderbufferStorage(GL30.GL_RENDERBUFFER, GL14.GL_DEPTH_COMPONENT16, width, height);
			GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, width, height, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, 0);
		} else {
			GL30.glRenderbufferStorageMultisample(GL30.GL_RENDERBUFFER, fbo.samples, GL14.GL_DEPTH_COMPONENT16, width, height);
			GL32.glTexImage2DMultisample(GL32.GL_TEXTURE_2D_MULTISAMPLE, fbo.samples, GL30.GL_RGBA16F, width, height, true);
		}
		GL30.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, target, fbo.texture.getTexture(), 0);
		GL11.glDrawBuffer(GL30.GL_COLOR_ATTACHMENT0);
		GL30.glFramebufferRenderbuffer(GL30.GL_FRAMEBUFFER, GL30.GL_DEPTH_ATTACHMENT, GL30.GL_RENDERBUFFER, fbo.renderbuffer);
		boolean isComplete = GL30.glCheckFramebufferStatus(GL30.GL_FRAMEBUFFER) == GL30.GL_FRAMEBUFFER_COMPLETE;
		GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
		Texture.unbind();
		if(!isComplete) {
			fbo.texture.destroy();
			throw new Exception("fbo创建失败");
		}
		return fbo;
	}
	
	public void begin() {
		GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, framebuffer);
		GL11.glClear(GL11.GL_DEPTH_BUFFER_BIT | GL11.GL_COLOR_BUFFER_BIT);
	}
	
	public static void finish() {
		GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
	}
	
	public void destroy() {
		this.texture.destroy();
		this.texture = null;
		ClearUpGLRubbishManager.push(this);
	}

	public int getWidth() {
		return width;
	}

	public int getHeight() {
		return height;
	}

	public Texture getTexture() {
		return texture;
	}

	public int getSamples() {
		return samples;
	}

	@Override
	public void clearup() {
		GL30.glDeleteFramebuffers(framebuffer);
		GL30.glDeleteRenderbuffers(renderbuffer);
	}
	
}
